#include "MCPayoff.h"

namespace userPayoff
{
	class userFunctions : public EQMCBase
	{
		/* declare variables/functions */
		int nReturns, stkIndex;
		double p, R, cap, floor, weightSum, targetVol, low, high, annFac, alpha, fundingRate;
		vector<double> weights, bsk, yearFractions;
		/* initialise variables */
	public:
		void initData()
		{
			nReturns = getInt("nReturns", -1);
			annFac = getDouble("annFac", 254.0);
			//weights = new double[nReturns]; bsk = new double[nObs + 1]; yearFractions = new double[nObs + 1];
			weights.resize(nReturns); 
			bsk.resize(nObs + 1);
			yearFractions.resize(nObs + 1);
			alpha = getDouble("weightPower", 1.0 - 3.0 / (double)nReturns);
			weightSum = alpha;
			weights[0] = weightSum;
			for (int t = 1; t < nReturns; ++t)
			{
				weights[t] = alpha * weights[t - 1];
				weightSum += weights[t];
			}
			fundingRate = getDouble("fundingRate", 0.0);
			string dcb = getString("daycountBasis", "ACT365");
			for (int t = 1; t <= nObs; ++t)
			{
				yearFractions[t] = fundingRate != 0 ? yearFraction(t - 1, t, dcb) : 0.0;
			}
			targetVol = getDouble("target", 0.0);
			cap = getDouble("cap", 1000.0);
			floor = getDouble("floor", 0.0);
			if (Math.Abs(floor) > 1e-8) { throw new Exception(("Error: floor must be set to 0.0, not " + to_string(floor) + ", use strikeFactor instead").c_str()); }
			low = getDouble("lowerLimit", 1.0);
			high = getDouble("upperLimit", 1.0);
			p = getDouble("participation", 1.0);
			R = getDouble("redemptionCash", 0.0);
			for (int t = 1; t <= nObs; ++t)
			{
				if (isEvent(t, SCH1))
				{
					stkIndex = t - 1;
					if (stkIndex < nReturns) { throw new Exception("Error: sch1 is too early, not enough obs for 1st realised vol computation"); }
				}
			}
		}
		/* auxiliary function */
		double option(double S) const
		{
			if (derivativeType == PUT) { return Math.Max(strikeFactor - S, 0.0); }
			if (derivativeType == CALL) { return Math.Max(S - strikeFactor, 0.0); }
			if (derivativeType == FWD) { return (S - strikeFactor); }
			throw new Exception("Error: Invalid derivativeType!");
		}
		/* define payout */
double calcCorePayoffBi(bool isPastPayoff) const
{
	double finalPayoff = 0.0;
	if (!isEvent(nObs, SCH2)) { throw new Exception("Error: sch2 must be present on expDate"); }
	bool isStruck = false;
	double currSum = 0.0;
	double currTW;
	double currSpot = 1.0;
	double currExp = 0.0; 
	//double* bsk = (double*)alloca((nObs + 1)*sizeof(double));
	bsk[0] = 0.0;
	for (int t = 0; t <= nObs; ++t)
	{
		bsk[t] = 0;
		for (int u = 0; u < nUnds; ++u)
		{
			bsk[t] += weight(u) * NS(u, t) / NS(u, stkIndex + 1);
		}
	}
	for (int t = 1; t <= nObs; t++)
	{
		if (isStruck)
		{
			currSpot = currSpot * (1.0 + currExp * (bsk[t] / bsk[t - 1] - 1.0 - yearFractions[t] * fundingRate));
			currTW = Math.Min(cap, targetVol / Math.Sqrt(currSum * annFac / weightSum));
			if (currTW < low * currExp || currTW > high * currExp)
				currExp = currTW;
			double perf = Math.Log(bsk[t - nReturns] / bsk[t - nReturns - 1]);
			currSum -= perf * perf * weights[nReturns - 1];
			currSum *= weights[0];
			perf = Math.Log(bsk[t] / bsk[t - 1]);
			currSum += weights[0] * perf * perf;
		}
		if (t == stkIndex)
		{
			for (int tt = 0; tt < nReturns; ++tt)
			{
				double perf = Math.Log(bsk[t - tt] / bsk[t - tt - 1]);
				currSum += weights[tt] * perf * perf;
			}
			isStruck = true;
		}
		if (isEvent(t, SCH2))
		{
			finalPayoff += currSpot;
		}
	}
	finalPayoff /= nSch2s;
	return R + p * option(finalPayoff);
}

	};
}

